package hwdroid.widget;

import hwdroid.widget.item.DrawableText2Item;
import hwdroid.widget.item.Item;
import hwdroid.widget.item.Item.Type;
import hwdroid.widget.item.SeparatorItem;
import hwdroid.widget.item.Text2Item;
import hwdroid.widget.item.TextItem;
import hwdroid.widget.itemview.ItemView;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

/**
 * <p>
 * A ListAdapter that acts like an ArrayAdapter. An ItemAdapter manages a
 * ListView that is backed by an array of {@link hwdroid.widget.item.Item}s. This is more advanced
 * than a simple ArrayAdapter because it handles different types of itemviews
 * internally. Adding, removing items from the internal array is also possible.
 * </p>
 * <p>
 * The ListView can be notified of underlying data changes manually using
 * notifyDataSetChanged or automatically using the
 * {@link #setNotifyOnChange(boolean)} method.
 * </p>
 * <p>
 * Finally, an ItemAdapter can be created via XML code using the
 * {@link #createFromXml(android.content.Context, int)} method. This is a very powerful feature
 * when you want to display static data or if you want to pre-populate your
 * ItemAdapter.
 * </p>
 */
public class ItemAdapter extends BaseAdapter {

    private static final int DEFAULT_MAX_VIEW_TYPE_COUNT = 10;

    private static class TypeInfo {
        int count;
        int type;
    }

    private List<Item> mItems;
    private HashMap<Class<? extends Item>, TypeInfo> mTypes;
    private Context mContext;

    private boolean mNotifyOnChange;
    private int mMaxViewTypeCount;

    private Type mItemType;

    private boolean mSubTextSingleLine = true;

//    private ArrayList<SelectedValue> mSelectedItems;
//    private int mSelectedCount;
//
//    public class SelectedValue {
//    	public boolean isSelected = false;
//
//    	public SelectedValue(boolean value) {
//    		isSelected = value;
//    	}
//    }

    /**
     * Constructs an empty ItemAdapter.
     *
     * @param context The context associated with this array adapter.
     */
    public ItemAdapter(Context context) {
        this(context, new ArrayList<Item>());
    }

    /**
     * Constructs an ItemAdapter using the specified items.
     * <p>
     * <em><strong>Note</strong>: Using this constructor implies the internal array will be
     * immutable. As a result, adding or removing items will result in an
     * exception.</em>
     * </p>
     *
     * @param context The context associated with this array adapter.
     * @param items   The array of {@link hwdroid.widget.item.Item}s use as underlying data for this
     *                ItemAdapter
     */
    public ItemAdapter(Context context, Item[] items) {
        this(context, Arrays.asList(items), DEFAULT_MAX_VIEW_TYPE_COUNT);
    }

    /**
     * Constructs an ItemAdapter using the specified items.
     * <p/>
     *
     * @param context The context associated with this array adapter.
     * @param items   The list of {@link hwdroid.widget.item.Item}s used as data for this ItemAdapter
     */
    public ItemAdapter(Context context, List<Item> items) {
        this(context, items, DEFAULT_MAX_VIEW_TYPE_COUNT);
    }

    /**
     * Constructs an ItemAdapter using the specified items.
     * <p>
     * <em><strong>Note</strong>: Using this constructor implies the internal array will be
     * immutable. As a result, adding or removing items will result in an
     * exception.</em>
     * </p>
     * <p>
     * <em><strong>Note</strong>: A ListAdapter doesn't handle variable view type
     * count (even after a notifyDataSetChanged). An ItemAdapter handles several
     * types of cell are therefore use a trick to overcome the previous problem.
     * This trick is to fool the ListView several types exist. If you already
     * know the number of item types you'll possibly have, simply set it using this method</em>
     * </p>
     *
     * @param context          The context associated with this array adapter.
     * @param items            The array of {@link hwdroid.widget.item.Item}s use as underlying data for this
     *                         ItemAdapter
     * @param maxViewTypeCount The maximum number of view type that may be
     *                         generated by this ItemAdapter
     */
    public ItemAdapter(Context context, Item[] items, int maxViewTypeCount) {
        this(context, Arrays.asList(items), maxViewTypeCount);
    }

    /**
     * Constructs an ItemAdapter using the specified items.
     * <p>
     * <em><strong>Note:</strong> A ListAdapter doesn't handle variable view type
     * count (even after a notifyDataSetChanged). An ItemAdapter handles several
     * types of cell are therefore use a trick to overcome the previous problem.
     * This trick is to fool the ListView several types exist. If you already
     * know the number of item types you'll possibly have, simply set it using this method</em>
     * </p>
     *
     * @param context          The context associated with this array adapter.
     * @param items            The list of Items used as data for this ItemAdapter
     * @param maxViewTypeCount The maximum number of view type that may be
     *                         generated by this ItemAdapter
     */
    public ItemAdapter(Context context, List<Item> items, int maxViewTypeCount) {
        mContext = context;
        mItems = items;
        mItemType = Type.NORMAL_MODE;

        initSelectedArray(mItems);
        mTypes = new HashMap<Class<? extends Item>, TypeInfo>();
        mMaxViewTypeCount = Integer.MAX_VALUE;

        for (Item item : mItems) {
            addItem(item);
        }

        mMaxViewTypeCount = Math.max(1, Math.max(mTypes.size(), maxViewTypeCount));
    }

    private void addItem(Item item) {
        final Class<? extends Item> klass = item.getClass();
        TypeInfo info = mTypes.get(klass);

        if (info == null) {
            final int type = mTypes.size();
            if (type >= mMaxViewTypeCount) {
                throw new RuntimeException("This ItemAdapter may handle only " + mMaxViewTypeCount + " different view types.");
            }
            final TypeInfo newInfo = new TypeInfo();
            newInfo.count = 1;
            newInfo.type = type;
            mTypes.put(klass, newInfo);
        } else {
            info.count++;
        }
    }

    private void removeItem(Item item) {
        final Class<? extends Item> klass = item.getClass();
        TypeInfo info = mTypes.get(klass);

        if (info != null) {
            info.count--;
            if (info.count == 0) {
                // TODO cyril: Creating a pool to keep all TypeInfo instances
                // could be a great idea in the future.
                mTypes.remove(klass);
            }
        }
    }

    /**
     * Returns the context associated with this array adapter. The context is
     * used to create Views from the resource passed to the constructor.
     *
     * @return The Context associated to this ItemAdapter
     */
    public Context getContext() {
        return mContext;
    }

    /**
     * Returns the current number of different views types used in this
     * ItemAdapter. Having a <em>getCurrentViewTypeCount</em> equal to
     * <em>getViewTypeCount</em> means you won't be able to add a new type of
     * view in this adapter (The Adapter class doesn't allow variable view type
     * count).
     *
     * @return The current number of different view types
     */
    public int getActualViewTypeCount() {
        return mTypes.size();
    }

    /**
     * Adds the specified object at the end of the array.
     *
     * @param item The object to add at the end of the array.
     */
    public void add(Item item) {
        mItems.add(item);

        //mSelectedItems.add(mItems.indexOf(item), new SelectedValue(false));
        addItem(item);
        if (mNotifyOnChange) {
            notifyDataSetChanged();
        }
    }

    /**
     * Inserts the specified object at the specified index in the array.
     *
     * @param item  The object to insert into the array.
     * @param index The index at which the object must be inserted.
     */
    public void insert(Item item, int index) {
        mItems.add(index, item);
        //mSelectedItems.add(index, new SelectedValue(false));
        addItem(item);
        if (mNotifyOnChange) {
            notifyDataSetChanged();
        }
    }

    /**
     * Removes the specified object from the array.
     *
     * @param item The object to remove.
     */
    public void remove(Item item) {
        //int pos = mItems.indexOf(item);
        if (mItems.remove(item)) {
            //mSelectedItems.remove(pos);
            removeItem(item);
            if (mNotifyOnChange) {
                notifyDataSetChanged();
            }
        }
    }

    /**
     * Remove all elements from the list.
     */
    public void clear() {
        mItems.clear();
        //mSelectedItems.clear();
        mTypes.clear();
        if (mNotifyOnChange) {
            notifyDataSetChanged();
        }
    }

    /**
     * Sorts the content of this adapter using the specified comparator.
     *
     * @param comparator The comparator used to sort the objects contained in
     *                   this adapter.
     */
    public void sort(Comparator<? super Item> comparator) {
        Collections.sort(mItems, comparator);
        if (mNotifyOnChange) {
            notifyDataSetChanged();
        }
    }

    /**
     * Control whether methods that change the list ({@link #add},
     * {@link #insert}, {@link #remove}, {@link #clear}) automatically call
     * notifyDataSetChanged(). If set to false, caller must manually call
     * notifyDataSetChanged() to have the changes reflected in the attached
     * view. The default is true, and calling notifyDataSetChanged() resets the
     * flag to true.
     *
     * @param notifyOnChange if true, modifications to the list will
     *                       automatically call notifyDataSetChanged()
     */
    public void setNotifyOnChange(boolean notifyOnChange) {
        mNotifyOnChange = notifyOnChange;
    }

    /**
     * Creates an ItemAdapter from a given resource ID
     *
     * @param context The Context in which the ItemAdapter will be used in
     * @param xmlId   The resource ID of an XML file that describes a set of
     *                {@link hwdroid.widget.item.Item}
     * @return a new ItemAdapter constructed with the content of the file
     * pointed by <em>xmlId</em>
     * @throws org.xmlpull.v1.XmlPullParserException
     * @throws java.io.IOException
     */
    public static ItemAdapter createFromXml(Context context, int xmlId) throws XmlPullParserException, IOException {
        return createFromXml(context, context.getResources().getXml(xmlId));
    }

    /**
     * Creates an ItemAdapter from a given XML document. Called on a parser
     * positioned at a tag in an XML document, tries to create an ItemAdapter
     * from that tag.
     *
     * @param context The Context in which the ItemAdapter will be used in
     * @param parser  The XmlPullParser
     * @return a new ItemAdapter constructed with the content of the file
     * pointed by <em>xmlId</em>
     * @throws org.xmlpull.v1.XmlPullParserException
     * @throws java.io.IOException
     */
    public static ItemAdapter createFromXml(Context context, XmlPullParser parser) throws XmlPullParserException, IOException {
        AttributeSet attrs = Xml.asAttributeSet(parser);

        int type;
        while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
            // Empty loop
        }

        if (type != XmlPullParser.START_TAG) {
            throw new XmlPullParserException("No start tag found");
        }

        if (!parser.getName().equals("item-array")) {
            throw new XmlPullParserException("Unknown start tag. Should be 'item-array'");
        }

        final List<Item> items = new ArrayList<Item>();
        final int innerDepth = parser.getDepth() + 1;
        final Resources r = context.getResources();

        int depth;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            if (depth > innerDepth) {
                continue;
            }

            String name = parser.getName();
            Item item;
            if (name.equals("text-item")) {
                item = new TextItem();
            } else if (name.equals("separator-item")) {
                item = new SeparatorItem();
            } else if (name.equals("drawable-text-2-item")) {
                item = new DrawableText2Item();
            } else if (name.equals("text-2-item")) {
                item = new Text2Item();
            } else {
                // TODO Remove that so that we can extend from
                // ItemAdapter and creates our own items via XML?
                throw new XmlPullParserException(parser.getPositionDescription() + ": invalid item tag " + name);
            }

            // TODO Here we should call a method that children may
            // override to be able to create our own Items

            if (item != null) {
                item.inflate(r, parser, attrs);
                items.add(item);
            }
        }

        return new ItemAdapter(context, items);
    }

    /**
     * get list count.
     */
    public int getCount() {
        return mItems.size();
    }

    /**
     * get item instance.
     *
     * @param position
     */
    public Object getItem(int position) {
        return mItems.get(position);
    }

    /**
     * get item position.
     */
    public long getItemId(int position) {
        return position;
    }

    /**
     * get itemvview type.
     */
    @Override
    public int getItemViewType(int position) {
        return mTypes.get(getItem(position).getClass()).type;
    }

    /**
     * Check whether the formulation of the item is enable state
     *
     * @param position the formulation of the item.
     */
    @Override
    public boolean isEnabled(int position) {
        return ((Item) getItem(position)).isEnabled();
    }

    /**
     * get view type count.
     */
    @Override
    public int getViewTypeCount() {
        return mMaxViewTypeCount;
    }

    public View getView(int position, View convertView, ViewGroup parent) {

        final Item item = (Item) getItem(position);
        ItemView cell = (ItemView) convertView;

        if (cell == null) {
            cell = item.newView(mContext, null);
            cell.prepareItemView();
            cell.setSubTextSingleLine(mSubTextSingleLine);
        }

        if (getTypeMode() != Type.NORMAL_MODE) {
            item.setTypeMode(getTypeMode());
        }

        cell.setObject(item);

        return (View) cell;
    }

    /**
     * set mode type.
     *
     * @param itemType
     */
    public void setTypeMode(Type itemType) {
        mItemType = itemType;
    }

    /**
     * get mode type.
     *
     * @return
     */
    public Type getTypeMode() {
        return mItemType;
    }

    private void initSelectedArray(List<Item> mItems) {
    }

    /**
     * note: don't use
     *
     * @param pos
     */
    public void setSelectedItem(int pos) {
        //do nothing.
    }

    /**
     * the lists are selected(or no selected) when called.
     *
     * @param select : true: selected, false: no selected.
     */
    public void doAllSelected(boolean select) {
        for (int i = 0; i < mItems.size(); i++) {
            mItems.get(i).setChecked(select);
            Log.i("hw", "doAllSelected() i = " + i + " value = " + select);
        }

//    	if(select == true) {
//    		mSelectedCount = mItems.size();
//    	} else {
//    		mSelectedCount = 0;
//    	}
    }

    /**
     * get selected status list .
     *
     * @return a boolean arrays.
     */
    public boolean[] getSelectedList() {
        if (mItems == null && mItems.size() < 1) return null;

        boolean[] selected = new boolean[mItems.size()];
        for (int i = 0; i < mItems.size(); i++) {
            selected[i] = mItems.get(i).isChecked();
        }

        return selected;
    }

    /**
     * get selected status
     *
     * @param pos
     * @return true is selected, false is no selected.
     */
    public boolean getSelectedStatus(int pos) {
        return mItems.get(pos).isChecked();
    }

    /**
     * get selected item's count.
     *
     * @return
     */
    public int getSelectedCounts() {
        int count = 0;

        for (int i = 0; i < mItems.size(); i++) {
            if (mItems.get(i).isChecked()) {
                count++;
            }
        }
        return count;
    }

    /**
     * set sub text to single line(true / false)
     *
     * @param enabled true is single line, false is multi line.
     */
    public void setSubTextSingleLine(boolean enabled) {
        mSubTextSingleLine = enabled;
    }
}
